// 
// Cai dat cac phuong thuc trong class INPUT_WAVE
// 
#include "stdafx.h"
#include <cstdio>
#include <cstdlib>  // ham exit()
#include <string.h>

#include "InputWaveFile.h"


#define NORMALIZE_16_BIT 1 //32768.0 // from [-2^15+1, 2^15] to [-1,1]
#define NORMALIZE_8_BIT 1 //128.0 // from [-2^7+1, 2^7] to [-1,1]
#define MAXVALUE	9223372036854775807	

/********************************************/ 
INPUT_WAVE::INPUT_WAVE()
{
}

/********************************************/ 
INPUT_WAVE::INPUT_WAVE(char *file_name)
{   
	int i;
	FILE *fp_wave; // file pointer
	unsigned int file_operation_state; // wave file operation state
	char output_buffer[80]; //ouput buffer

	WAV_FILE_HEADER *wav_file_header;  // wave file header
	DATA_CHUNK_HEADER *data_chunk_header; // data chunk header
	short int *uptr;
	unsigned char *cptr;
	int data_chunk_id; // so thu tu cua chunk tiep theo can doc
	long int fp_moving_distance; // so vi tri dich chuyen file pointer

	char *wbuff; // buffer chua data chunk hay chua cac mau
	int wbuff_len; // tong so mau 

	// set defaults 
	samples_array = NULL;
	current_samples = 0; 
	total_number_of_samples_in_wave_file = 0;

	// allocate wav header 
	wav_file_header = new WAV_FILE_HEADER;
	data_chunk_header = new DATA_CHUNK_HEADER;
	if(wav_file_header==NULL){ printf("khong the doc header cua file wav\n"); exit(-1); }
	if(data_chunk_header==NULL){ printf("khong the doc header cua data chunk dau tien\n"); exit(-1); }

	/* open wav file */ 
	fp_wave = fopen(file_name,"rb");
	if(fp_wave==NULL){ printf("khong the mo file wav\n"); exit(-1); }

	/* Read 36 bytes header */    
	file_operation_state = fread((void *)wav_file_header,sizeof(WAV_FILE_HEADER),(size_t)1,fp_wave);
	if(file_operation_state!=1){ printf("khong the doc file wav\n"); exit(-1); }
	// Bay gio Header da o trong wav struct

	// Checking Header format
	//  "RIFF" file
	for(i=0;i<4;i++) output_buffer[i] = wav_file_header->riff_string[i];
	output_buffer[4] = 0;
	if(strcmp(output_buffer,"RIFF")!=0){ printf("Khong phai dinh dang RIFF\n"); exit(-1); }

	// Check "WAVE" file
	for(i=0;i<4;i++) output_buffer[i] = wav_file_header->wave_string[i];
	output_buffer[4] = 0;
	if(strcmp(output_buffer,"WAVE")!=0){ printf("Khong phai dinh dang WAVE\n"); exit(-1); }

	// Check "fmt" of subchunk
	for(i=0;i<3;i++) output_buffer[i] = wav_file_header->fmt_string[i];
	output_buffer[3] = 0;
	if(strcmp(output_buffer,"fmt")!=0){ printf("Khong co fmt\n"); exit(-1); }

	// audio format tag must =  1
	if(wav_file_header->wave_file_format_tag!=1){ printf("Khong co format tag \n"); exit(-1); }

	// chi chap nhan luong tu 8 hoac 16 bit
	if( (wav_file_header->number_of_bits_per_sample != 16) && (wav_file_header->number_of_bits_per_sample != 8) ){
		printf("Khong the doc so bit tren mot mau\n"); exit(-1); }


	// Khong doc cac phan con lai cua header; 
	// phan cua header da duoc doc co do dai Header_size - 20 bytes. 
	// 20 bytes nay gom co: "RIFF", chunksize (long int); "WAVE", "FMT ", SubChunkSize1

	//pcm_header_len=16,sizeof(WAV_FILE_HEADER)=36
	fp_moving_distance = wav_file_header->pcm_header_len - (sizeof(WAV_FILE_HEADER) - 20); 
	// chuyen con tro tien toi dau data chunk
	file_operation_state = fseek(fp_wave,fp_moving_distance,SEEK_CUR);
	if(file_operation_state!=0){ printf("Khong the di chuyen con tro file ve dau data chunk dau tien\n"); exit(-1); }

	// read chunks until a 'data' chunk is found 
	data_chunk_id = 1;
	while(data_chunk_id!=0){

		// check attempts 
		if(data_chunk_id>MAX_NUMBER_OF_DATA_CHUNKS){ printf("So data chunk qua nhieu\n"); exit(-1); }

		// read chunk header 
		// chunk header gom 8 bytes: "data", chunksize (long int)
		file_operation_state = fread((void *)data_chunk_header,sizeof(DATA_CHUNK_HEADER),(size_t)1,fp_wave);
		if(file_operation_state!=1){ printf("cant read chunk\n"); exit(-1); }

		// check chunk type 
		for(i=0;i<4;i++) output_buffer[i] = data_chunk_header->data_string[i];
		output_buffer[4] = 0;
		if(strcmp(output_buffer,"data")==0) break;

		// so thu tu cua chunk tiep theo can doc
		data_chunk_id++; 
		// chuyen sang chunk tiep theo
		file_operation_state = fseek(fp_wave,data_chunk_header->data_chunk_length,SEEK_CUR); 
		if(file_operation_state!=0){ printf("Khong the di chuyen con tro file ve dau data chunk tiep theo\n"); exit(-1); }
	}

	/* do dai chunk data */ 
	wbuff_len = data_chunk_header->data_chunk_length;

	// find number of samples 
	// so byte trong data chunk 
	// so mau = tong so byte / (so byte/mau)
	total_number_of_samples_in_wave_file = data_chunk_header->data_chunk_length;
	total_number_of_samples_in_wave_file /= wav_file_header->number_of_bits_per_sample / 8; 

	/* allocate new buffers */ 
	wbuff = new char [wbuff_len];
	if(wbuff==NULL){ printf("Khong the cap them bo nho\n"); exit(-1); }

	//  renew g_data_in: if(samples_array!=NULL) delete samples_array; 
	samples_array = new double [total_number_of_samples_in_wave_file];
	if(samples_array==NULL){ printf("Khong the cap them bo nho\n"); exit(-1); }


	/* doc cac mau */ 
	file_operation_state = fread((void *)wbuff,wbuff_len,(size_t)1,fp_wave);//luu du lieu vao wbuff
	if(file_operation_state!=1){ printf("cant read wbuff\n"); exit(-1); }


	//this correcet form
	// Calculate the scaling factor for converting to a normalised double
	double FloatOffset;
	double FloatScale;
	if (wav_file_header->number_of_bits_per_sample > 8)
	{
		// If more than 8 validBits, data is signed
		// Conversion required multiplying by magnitude of max positive value
		FloatOffset = 0;
		FloatScale = MAXVALUE >> (64 - wav_file_header->number_of_bits_per_sample);
	}

	else
	{
		// Else if 8 or less validBits, data is unsigned
		// Conversion required dividing by max positive value
		FloatOffset = 1;
		FloatScale = 0.5 * ((1 << wav_file_header->number_of_bits_per_sample) - 1);
	}

	// convert data 
	// 16 bits/sample
	if(wav_file_header->number_of_bits_per_sample == 16){
		uptr = (short *) wbuff;
		for(i=0;i<total_number_of_samples_in_wave_file;i++) samples_array[i] = FloatOffset + (double) (uptr[i])/FloatScale;
	}

	// 8 bits/sample
	else{
		cptr = (unsigned char *) wbuff;
		for(i=0;i<total_number_of_samples_in_wave_file;i++) samples_array[i] = FloatOffset +(double) (cptr[i])/FloatScale;
	}

	// save demographics 
	// tan so lau mau
	sampling_frequency_hz = (double) (wav_file_header->number_of_samples_per_second);
	// so bit/mau
	number_of_bits_per_sample = wav_file_header->number_of_bits_per_sample;
	number_of_channels = wav_file_header->number_of_channels;

	double* mono_samples_array;
	if (number_of_channels == 2)
	{
		total_number_of_samples_in_wave_file =total_number_of_samples_in_wave_file/2;
		mono_samples_array = new double[total_number_of_samples_in_wave_file];

		mono_samples_array = ConvertStereoToMono(samples_array, total_number_of_samples_in_wave_file);
		samples_array = mono_samples_array;

	}
	

	printf("\nLoaded WAV File: %s\n",file_name);
	printf(" Sample Rate = %1.0lf (Hz)\n",sampling_frequency_hz);
	printf(" Number of Samples = %ld\n",total_number_of_samples_in_wave_file);
	printf(" Bits Per Sample = %d\n",number_of_bits_per_sample);
	printf(" Number of Channels = %d\n\n",number_of_channels);

	// reset buffer stream index 
	current_samples = 0;

	// be polite - clean up 
	if(wbuff!=NULL) delete wbuff;
	//if(mono_samples_array!=NULL) delete mono_samples_array;
	if(wav_file_header!=NULL) delete wav_file_header;
	if(data_chunk_header!=NULL) delete data_chunk_header;
	fclose(fp_wave);

}

/********************************************/ 
INPUT_WAVE::~INPUT_WAVE(){ }

/********************************************/
double *INPUT_WAVE::get_samples_array()
{
	return samples_array;//tra ve du lieu trong truong data vua doc
}

/********************************************/ 
long int INPUT_WAVE::get_number_of_samples(){ return total_number_of_samples_in_wave_file; }//tra ve

/********************************************/
int INPUT_WAVE::get_number_of_channels(){ return number_of_channels; }

/********************************************/ 
int INPUT_WAVE::get_number_of_bits_per_samples(){ return number_of_bits_per_sample; }

/********************************************/ 
double INPUT_WAVE::get_sample_rate_hz(){ return sampling_frequency_hz; }

/********************************************/ 
int INPUT_WAVE::is_there_more_data()
{ 
	if(current_samples>=total_number_of_samples_in_wave_file) return 0;

	return 1;
}  

/********************************************/ 
double INPUT_WAVE::read_current_input()
{ 
	if( (samples_array==NULL) || (total_number_of_samples_in_wave_file<=0) || (current_samples<0) )
	{
		printf("input file not ready (or not loaded)!!!\n");
		exit(1);
	}
	if(current_samples>=total_number_of_samples_in_wave_file)
	{
		printf("attempt to read past end of input buffer!\n");
		exit(1);
	}

	return( samples_array[current_samples++] );
} 

/********************************************/
void INPUT_WAVE::cho_tin_hieu_qua_bo_loc_hieu_chinh()
{
	long  i;
	double * temp_array = new double [total_number_of_samples_in_wave_file];
	if(temp_array==NULL){ printf("Khong the cap them bo nho\n"); exit(-1); }

	for(i = total_number_of_samples_in_wave_file; i>0; i--)
		temp_array[i] = (double) (samples_array[i] - HE_SO_BO_LOC_HIEU_CHINH*samples_array[i-1]);

	for(i = total_number_of_samples_in_wave_file; i>0; i--)
		samples_array[i] = temp_array[i];

	//if (temp_array!=NULL) delete temp_array;
}
/********************************************/
double* INPUT_WAVE::get_samples(long int m, int length)
{
	// Kiem tra: m+length
	if (total_number_of_samples_in_wave_file < m + length) {printf("Doc du lieu qua pham vi ton tai\n"); exit(-1); }
	// Tra lai gia tri tin hieu samples_array[m:n-1];
	long int i;
	double *output = new double[length];
	for(i=0;i<length;i++)
	{
		output[i] = samples_array[m+i];
	}
	// memcpy(output,samples_array+m,length);
	return output;
}

/// <summary>
/// Low pass filter for downsampling 44100Hz to 5512Hz
/// </summary>
double _lpFilter44[] =  {
	-6.4796e-04, -1.4440e-03, -2.7023e-03, -4.4407e-03, -6.1915e-03, -6.9592e-03
	, -5.3707e-03,  2.3907e-18,  1.0207e-02,  2.5522e-02,  4.5170e-02,  6.7289e-02
	,  8.9180e-02,  1.0778e-01,  1.2027e-01,  1.2467e-01,  1.2027e-01,  1.0778e-01
	,  8.9180e-02,  6.7289e-02,  4.5170e-02,  2.5522e-02,  1.0207e-02,  2.3907e-18
	, -5.3707e-03, -6.9592e-03, -6.1915e-03, -4.4407e-03, -2.7023e-03, -1.4440e-03
	, -6.4796e-04

};


/// <summary>
/// Convolve a sample with a filter at a given point
/// </summary>
/// <param name="samples">Samples to be convolved</param>
/// <param name="startIndex">Starting index of a sample</param>
/// <param name="filter">Filter used in convolving</param>
/// <returns>Convolved sample</returns>
double INPUT_WAVE::ConvolvePoint(int startIndex, double filter[])
{
	double sum = 0;
	int len = sizeof(filter) / sizeof(filter[0]);
	for (int i = 0; i < len; i++)
		sum += (double) (samples_array[i + startIndex] * filter[i]);

	return sum;
}

double* INPUT_WAVE::down_sample()
{
	const int multiplier = 8;
	int nsamples = this->get_number_of_samples();
	int newnsamples = nsamples/multiplier - 3;
	double* new_samples_array = new double[newnsamples];
	for (int i = 0; i < newnsamples; i++)
		new_samples_array[i] = ConvolvePoint(multiplier*i, _lpFilter44);
	return new_samples_array;
}

double* INPUT_WAVE::ConvertStereoToMono(double* data, int length)
{
	double* mono = new double[length];
	for (int i = 0; i < length; i++)
	{
		double left  = data[2 * i];
		double right  = data[2 * i + 1];
		mono[i] = (left + right) / 2;
	}
	return mono;
}

Dictionary INPUT_WAVE::compute_Spectrogram()
{
	STFT st = STFT();
	RingMatrix FullSpect = st.getSTFT(samples_array, get_number_of_samples(), 16384, this->sampling_frequency_hz);
	Dictionary HighestSpec = st.getHighestSTFT(FullSpect, sampling_frequency_hz, 150);
	
	return HighestSpec;
}